package automaton;

import java.awt.*;
import javax.swing.*;

import automaton.rule.AutomatoCelularRegra;
import automaton.rule.AutomatoCelularRegraGameLife;
import automaton.view.*;



/**
 * Compreende um automato celular bidimensional com regras do Jogo da Vida (Game of Life).
 * O Automato possui dois estados: On = 1 ou Off = 0.
 * 
 * A classe herda JFrame porque contem em o desenho do automato.
 * 
 * No desenho, cada automato eh representado por um quadrado preenchido se este estiver no estado On
 * e em branco se estiver Off.
 * 
 * @author Grupo 6
 */
public class AutomatoCelular2D extends JFrame{	


	/**
	 * 
	 */
	private static final long serialVersionUID = 4602184988692614851L;

	/**
	 * Tamanho do vetor de automatos
	 */
	private static final int TAMANHO_AUTOMATO_2D = 256;
	
	/**
	 * Texto de titulo da janela
	 */
	private static final String TEXTO_DE_TITULO = "Jogo da Vida";
	
	
	private BooleanMatrixCanvas canvas;
	
	/**
	 * Vetor dos automatos.
	 * Os automatos podem assumir estado 0 = Off ou 1=On, portanto sao modelados pelo tipo boolean.
	 * Esta matriz que sofre as modificacoes ao passar do tempo e contem a geracao atual dos automatos.
	 */
	BooleanMatrix automatos;
	
	/**
	 * Tempo em milissegundos para incrementar a geracao. 
	 * Deve ser configuravel pela tela.
	 */
	private int delayTempo;
	
	
	/**
	 * Guarda qual a geracao dos automatos atuais. Comeca na geracao 0, aquela 
	 * com os estados iniciais.
	 */
	private AutomatoCelularRegra regra;
	
	/**
	 * 
	 * @param width Frame width
	 * @param height Frame height
	 * @param fps Animation fps
	 */
	public AutomatoCelular2D(int width, int height, int fps) {
		super(AutomatoCelular2D.TEXTO_DE_TITULO);
		super.setSize(width, height);
		super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);		
		this.inicializarAutomatos();
		this.setFps(fps);

		BooleanMatrixCanvas canvas = new BooleanMatrixCanvas(this.automatos);
		this.add("Center",canvas);
		this.setCanvas(canvas);

	}
	
	/**
	 * Responsavel por definir o estado inicial dos automatos.
	 */
	private void inicializarAutomatos() {
		this.automatos = new BooleanMatrix(new Dimension(AutomatoCelular2D.TAMANHO_AUTOMATO_2D,AutomatoCelular2D.TAMANHO_AUTOMATO_2D));
		this.regra = new AutomatoCelularRegraGameLife(automatos);
		
		// Blinker na posicao 3x3 ate 3x5
		this.addBlinker(3, 3);
		
		// Sapo na posicao 10x20 ate 11x23
		this.addSapo(10,20);

		// Glider na posicao 100x100 ate 102x102
		this.addGlider(100,100);
		

		// Glider na posicao 200x200 ate 202x202
		this.addGlider(200,200);
		
		// Diehard na posicao 50x50
		this.addDiehard(75,25);

		// Acorn na posicao 50x50
		this.addAcorn(35,65);
		
		this.addDiehard(200,5);
		
		
	}
	
	/**
	 * Cria o loop de execucao do automato celular incrementando
	 * a geracao do automato quando o form termina de ser desenhado.
	 * 
	 * O desenho e feito com Active Rendering, adaptado a partir
	 * do esquema descrito no link {@link http://www.gamedev.net/page/resources/_/technical/general-programming/java-games-active-rendering-r2418}
	 * 
	 */
	public void run() {
		this.setVisible(true);
		while(true) {
			
			try {
				Thread.sleep(this.getDelayTempo());
			} catch(Exception exc) {
				exc.printStackTrace();
			}
			
			this.regra.incrementarGeracao();
			this.getCanvas().repaint();
		}
	}
	
	
	/**
	 * @return the delayTempo
	 */
	private int getDelayTempo() {
		return delayTempo;
	}

	/**
	 * @param delayTempo sets frames per second
	 */
	private void setFps(int fps) {
		this.delayTempo = 1000/fps;
	}
	
	// -- Objetos de automatos para serem inseridos
	
	/**
	 * Adiciona um blinker ao automato na posicao indicada.
	 * Para compreender o que eh um blinker, acessar {@link http://pt.wikipedia.org/wiki/Jogo_da_vida}
	 * 
	 * blinker = [*][*][*], tem comportamento de oscilador
	 * 
	 * @param pLinha linha da celula superior esquerda do blinker 
	 * @param pColuna coluna da celula superior esquerda do blinker 
	 * @throws IndexOutOfBoundsException disparada se houverem pontos do blinker que estao fora dos limites da matriz
	 */
	private void addBlinker(int pLinha, int pColuna) throws IndexOutOfBoundsException {
		this.automatos.setCell(pLinha,pColuna,true);
		this.automatos.setCell(pLinha,pColuna+1,true);
		this.automatos.setCell(pLinha,pColuna+2,true);
	}

	/**
	 * Adiciona um sapo ao automato na posi��o indicada.
	 * Para compreender o que eh um sapo, acessar {@link http://pt.wikipedia.org/wiki/Jogo_da_vida}
	 * 
	 * sapo = [ ][*][*][*]
	 *        [*][*][*][ ], tem comportamento oscilador
	 * 
	 * @param pLinha linha da celula superior esquerda do sapo
	 * @param pColuna coluna da celula superior esquerda do sapo
	 * @throws IndexOutOfBoundsException disparada se houverem pontos do sapo que estao fora dos limites da matriz
	 */
	private void addSapo(int pLinha, int pColuna) throws IndexOutOfBoundsException {
		this.automatos.setCell(pLinha,pColuna,false);
		this.automatos.setCell(pLinha,pColuna+1,true);
		this.automatos.setCell(pLinha,pColuna+2,true);
		this.automatos.setCell(pLinha,pColuna+3,true);
		this.automatos.setCell(pLinha+1,pColuna,true);
		this.automatos.setCell(pLinha+1,pColuna+1,true);
		this.automatos.setCell(pLinha+1,pColuna+2,true);
		this.automatos.setCell(pLinha+1,pColuna+3,false);
	}
	
	/**
	 * Adiciona um glider ao automato na posi��o indicada.
	 * Para compreender o que eh um glider, acessar {@link http://pt.wikipedia.org/wiki/Jogo_da_vida}
	 * 
	 * glider = [*][*][*]
	 * 			[*][ ][ ]
	 * 			[ ][*][ ], nave espacial (segue um caminho)
	 * 
	 * @param pLinha linha da celula superior esquerda do glide
	 * @param pColuna coluna da celula superior esquerda do glide
	 * @throws IndexOutOfBoundsException disparada se houverem pontos do glide que estao fora dos limites da matriz
	 */
	private void addGlider(int pLinha, int pColuna) throws IndexOutOfBoundsException {
		this.automatos.setCell(pLinha,pColuna,true);
		this.automatos.setCell(pLinha,pColuna+1,true);
		this.automatos.setCell(pLinha,pColuna+2,true);
		this.automatos.setCell(pLinha+1,pColuna,true);
		this.automatos.setCell(pLinha+1,pColuna+1,false);
		this.automatos.setCell(pLinha+1,pColuna+2,false);
		this.automatos.setCell(pLinha+2,pColuna,false);
		this.automatos.setCell(pLinha+2,pColuna+1,true);
		this.automatos.setCell(pLinha+2,pColuna+2,false);
			
	}
	
	/**
	 * Adiciona um diehard ao automato na posi��o indicada.
	 * Para compreender o que eh um diehard, acessar {@link http://pt.wikipedia.org/wiki/Jogo_da_vida}
	 * 
	 * diehard = [ ][ ][ ][ ][ ][ ][*][ ]
	 * 			 [*][*][ ][ ][ ][ ][ ][ ]
	 * 			 [ ][*][ ][ ][ ][*][*][*]
	 * 
	 * @param pLinha linha da celula superior esquerda do glide
	 * @param pColuna coluna da celula superior esquerda do glide
	 * @throws IndexOutOfBoundsException disparada se houverem pontos do diehard que estao fora dos limites da matriz
	 */
	private void addDiehard(int pLinha, int pColuna) throws IndexOutOfBoundsException {
		this.automatos.setCell(pLinha,pColuna,false);
		this.automatos.setCell(pLinha,pColuna+1,false);
		this.automatos.setCell(pLinha,pColuna+2,false);
		this.automatos.setCell(pLinha,pColuna+3,false);
		this.automatos.setCell(pLinha,pColuna+4,false);
		this.automatos.setCell(pLinha,pColuna+5,false);
		this.automatos.setCell(pLinha,pColuna+6,true);
		this.automatos.setCell(pLinha,pColuna+7,false);
		
		this.automatos.setCell(pLinha+1,pColuna,true);
		this.automatos.setCell(pLinha+1,pColuna+1,true);
		this.automatos.setCell(pLinha+1,pColuna+2,false);
		this.automatos.setCell(pLinha+1,pColuna+3,false);
		this.automatos.setCell(pLinha+1,pColuna+4,false);
		this.automatos.setCell(pLinha+1,pColuna+5,false);
		this.automatos.setCell(pLinha+1,pColuna+6,false);
		this.automatos.setCell(pLinha+1,pColuna+7,false);
		
		this.automatos.setCell(pLinha+2,pColuna,false);
		this.automatos.setCell(pLinha+2,pColuna+1,true);
		this.automatos.setCell(pLinha+2,pColuna+2,false);
		this.automatos.setCell(pLinha+2,pColuna+3,false);
		this.automatos.setCell(pLinha+2,pColuna+4,false);
		this.automatos.setCell(pLinha+2,pColuna+5,true);
		this.automatos.setCell(pLinha+2,pColuna+6,true);
		this.automatos.setCell(pLinha+2,pColuna+7,true);
		
	}
	
	/**
	 * Adiciona um acorn ao automato na posi��o indicada.
	 * Para compreender o que eh um acorn, acessar {@link http://pt.wikipedia.org/wiki/Jogo_da_vida}
	 * 
	 * acorn = [ ][*][ ][ ][ ][ ][ ]
	 * 		   [ ][ ][ ][*][ ][ ][ ]
	 * 		   [*][*][ ][ ][*][*][*]
	 * 
	 * @param pLinha linha da celula superior esquerda do acorn
	 * @param pColuna coluna da celula superior esquerda do acorn
	 * @throws IndexOutOfBoundsException disparada se houverem pontos do acorn que estao fora dos limites da matriz
	 */
	private void addAcorn(int pLinha, int pColuna) throws IndexOutOfBoundsException {
		this.automatos.setCell(pLinha,pColuna,false);
		this.automatos.setCell(pLinha,pColuna+1,true);
		this.automatos.setCell(pLinha,pColuna+2,false);
		this.automatos.setCell(pLinha,pColuna+3,false);
		this.automatos.setCell(pLinha,pColuna+4,false);
		this.automatos.setCell(pLinha,pColuna+5,false);
		this.automatos.setCell(pLinha,pColuna+6,false);

		this.automatos.setCell(pLinha+1,pColuna,false);
		this.automatos.setCell(pLinha+1,pColuna+1,false);
		this.automatos.setCell(pLinha+1,pColuna+2,false);
		this.automatos.setCell(pLinha+1,pColuna+3,true);
		this.automatos.setCell(pLinha+1,pColuna+4,false);
		this.automatos.setCell(pLinha+1,pColuna+5,false);
		this.automatos.setCell(pLinha+1,pColuna+6,false);		

		this.automatos.setCell(pLinha+2,pColuna,true);
		this.automatos.setCell(pLinha+2,pColuna+1,true);
		this.automatos.setCell(pLinha+2,pColuna+2,false);
		this.automatos.setCell(pLinha+2,pColuna+3,false);
		this.automatos.setCell(pLinha+2,pColuna+4,true);
		this.automatos.setCell(pLinha+2,pColuna+5,true);
		this.automatos.setCell(pLinha+2,pColuna+6,true);
	}

	private BooleanMatrixCanvas getCanvas() {
		return canvas;
	}

	private void setCanvas(BooleanMatrixCanvas canvas) {
		this.canvas = canvas;
	}
	
	
	
}
